#include <random>
#include <bitcoin/bitcoin.hpp>

// The string we are searching for
/* const std::string search64 = "16jY7qLJnxb7CHZyqBP8qca9d51gAjyXQN"; */
/* const std::string search66 = "13zb1hQbWVsc2S7ZTZnP2G4undNNpdh5so"; */
/* const std::string search67 = "1BY8GQbnueYofwSuFAT3USAhGjPrkxDdW9"; */
/* const std::string search68 = "1MVDYgVaSN6iKKEsbzRUAYFrYJadLYZvvZ"; */
/* const std::string search69 = "19vkiEajfhuZ8bs8Zu2jgmC6oqZbWqhxhG"; */
const std::string search64 = "16jY7qLJnxb7CHZyqBP8qca9d51gAjy";
const std::string search66 = "13zb1hQbWVsc2S7ZTZnP2G4undNNpdh";
const std::string search67 = "1BY8GQbnueYofwSuFAT3USAhGjPrkxD";
const std::string search68 = "1MVDYgVaSN6iKKEsbzRUAYFrYJadLYZ";
const std::string search69 = "19vkiEajfhuZ8bs8Zu2jgmC6oqZbWqh";

// Generate a random secret key. A random 32 bytes.
bc::ec_secret random_secret(std::default_random_engine& engine);
bc::ec_secret random_secret_63_64(std::default_random_engine& engine);
bc::ec_secret random_secret_65_66(std::default_random_engine& engine);
bc::ec_secret random_secret_66_67(std::default_random_engine& engine);
bc::ec_secret random_secret_67_68(std::default_random_engine& engine);
bc::ec_secret random_secret_68_69(std::default_random_engine& engine);
// Extract the Bitcoin address from an EC secret.
std::string bitcoin_address(const bc::ec_secret& secret);
// Case insensitive comparison with the search string.
bool match_found(const std::string& address,const std::string search);

int main()
{
    // random_device on Linux uses "/dev/urandom"
    // CAUTION: Depending on implementation this RNG may not be secure enough!
    // Do not use vanity keys generated by this example in production
    std::random_device random;
    std::default_random_engine engine(random());
    
    // Loop continuously...
    while (true)
    {
        // Generate a random secret.
        /* bc::ec_secret secret = random_secret(engine); */
        bc::ec_secret secret64 = random_secret_63_64(engine);
        bc::ec_secret secret66 = random_secret_65_66(engine);
        bc::ec_secret secret67 = random_secret_66_67(engine);
        bc::ec_secret secret68 = random_secret_67_68(engine);
        bc::ec_secret secret69 = random_secret_68_69(engine);
        // Get the address.
        std::string address64 = bitcoin_address(secret64);
        std::string address66 = bitcoin_address(secret66);
        std::string address67 = bitcoin_address(secret67);
        std::string address68 = bitcoin_address(secret68);
        std::string address69 = bitcoin_address(secret69);
        // Does it match our search string? (1kid)
        /* if (1) */
        if (match_found(address64,search64))
        {
            // Success!
            std::cout << "generate Secret: " << bc::encode_base16(secret64) << std::endl;
            std::cout << "Found vanity address! " << address64 << std::endl;
            std::cout << "Secret: " << bc::encode_base16(secret64) << std::endl;
            bc::wallet::ec_public public_key(secret64);
            std::cout << "Public key: " << public_key.encoded() << std::endl;
            /* return 0; */
        }
        if (match_found(address66,search66))
        {
            // Success!
            std::cout << "generate Secret: " << bc::encode_base16(secret66) << std::endl;
            std::cout << "Found vanity address! " << address66 << std::endl;
            std::cout << "Secret: " << bc::encode_base16(secret66) << std::endl;
            bc::wallet::ec_public public_key(secret66);
            std::cout << "Public key: " << public_key.encoded() << std::endl;
            /* return 0; */
        }
        if (match_found(address67,search67))
        {
            // Success!
            std::cout << "generate Secret: " << bc::encode_base16(secret67) << std::endl;
            std::cout << "Found vanity address! " << address67 << std::endl;
            std::cout << "Secret: " << bc::encode_base16(secret67) << std::endl;
            bc::wallet::ec_public public_key(secret67);
            std::cout << "Public key: " << public_key.encoded() << std::endl;
            /* return 0; */
        }
        if (match_found(address68,search68))
        {
            // Success!
            std::cout << "generate Secret: " << bc::encode_base16(secret68) << std::endl;
            std::cout << "Found vanity address! " << address68 << std::endl;
            std::cout << "Secret: " << bc::encode_base16(secret68) << std::endl;
            bc::wallet::ec_public public_key(secret68);
            std::cout << "Public key: " << public_key.encoded() << std::endl;
            /* return 0; */
        }
        if (match_found(address69,search69))
        {
            // Success!
            std::cout << "generate Secret: " << bc::encode_base16(secret69) << std::endl;
            std::cout << "Found vanity address! " << address69 << std::endl;
            std::cout << "Secret: " << bc::encode_base16(secret69) << std::endl;
            bc::wallet::ec_public public_key(secret69);
            std::cout << "Public key: " << public_key.encoded() << std::endl;
            /* return 0; */
        }
    }
    // Should never reach here!
    return 0;
}

bc::ec_secret random_secret(std::default_random_engine& engine)
{
    // Create new secret...
    bc::ec_secret secret;
    // Iterate through every byte setting a random value...
    for (uint8_t& byte: secret)
        byte = engine() & 255;
    // Return result.
    return secret;
}

bc::ec_secret random_secret_63_64(std::default_random_engine& engine)
{
    // Create new secret...
    bc::ec_secret secret;
    // Iterate through every byte setting a random value...
    int i=0;
    for (uint8_t& byte: secret){
        i++;
        if(i==25)byte = engine() | 0x80 & 0xff;
        if(i>25)byte = engine() & 0xff;

    }
    /* std::cout << "generate Secret: " << bc::encode_base16(secret) << std::endl; */
    return secret;
}
bc::ec_secret random_secret_65_66(std::default_random_engine& engine)
{
    // Create new secret...
    bc::ec_secret secret;
    // Iterate through every byte setting a random value...
    int i=0;
    for (uint8_t& byte: secret){
        i++;
        if(i==26)byte = engine() | 0x20 & 0x3f;
        if(i>26)byte = engine() & 0xff;

    }
    /* std::cout << "generate Secret: " << bc::encode_base16(secret) << std::endl; */
    return secret;
}
bc::ec_secret random_secret_66_67(std::default_random_engine& engine)
{
    // Create new secret...
    bc::ec_secret secret;
    // Iterate through every byte setting a random value...
    int i=0;
    for (uint8_t& byte: secret){
        i++;
        if(i==26)byte = engine() | 0x40 & 0x7f;
        if(i>26)byte = engine() & 0xff;

    }
    /* std::cout << "generate Secret: " << bc::encode_base16(secret) << std::endl; */
    return secret;
}
bc::ec_secret random_secret_67_68(std::default_random_engine& engine)
{
    // Create new secret...
    bc::ec_secret secret;
    // Iterate through every byte setting a random value...
    int i=0;
    for (uint8_t& byte: secret){
        i++;
        if(i==26)byte = engine() | 0x80 & 0xff;
        if(i>26)byte = engine() & 0xff;

    }
    /* std::cout << "generate Secret: " << bc::encode_base16(secret) << std::endl; */
    return secret;
}
bc::ec_secret random_secret_68_69(std::default_random_engine& engine)
{
    // Create new secret...
    bc::ec_secret secret;
    // Iterate through every byte setting a random value...
    int i=0;
    for (uint8_t& byte: secret){
        i++;
        if(i==27)byte = engine() | 0x10 & 0x1f;
        if(i>27)byte = engine() & 0xff;

    }
    /* std::cout << "generate Secret: " << bc::encode_base16(secret) << std::endl; */
    return secret;
}

std::string bitcoin_address(const bc::ec_secret& secret)
{
    // Convert secret to payment address
    bc::wallet::ec_private private_key(secret);
    bc::wallet::payment_address payaddr(private_key);
    // Return encoded form.
    return payaddr.encoded();
}

bool match_found(const std::string& address,const std::string search)
{
    auto addr_it = address.begin();
    // Loop through the search string comparing it to the lower case
    // character of the supplied address.
    for (auto it = search.begin(); it != search.end(); ++it, ++addr_it)
        if (*it != std::tolower(*addr_it))
            return false;
    // Reached end of search string, so address matches.
    return true;
}
